// <copyright file="RockComponent.cs" company="SpaceRock Team Members">
// Project: SpaceRock
// File: RockComponent.cs (10,29 KB, 303 lines)
// Creation date: 2010-05-28 13:53
// Last modified: 2010-07-29 14:06
// </copyright>

#region Using directives
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Oops.Xna.Framework;
using Oops.Xna.Framework.Physics;
using Particle3DSample;
using SpaceRockTeam.SpaceRock.Data;
using System;
using System.Collections.Generic;
#endregion


namespace SpaceRockTeam.SpaceRock.Components
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class RockComponent : Microsoft.Xna.Framework.DrawableGameComponent, IExplosionListener
    {
        /// <summary>
        /// 
        /// </summary>
        private const float RockScale = 0.1f;

        /// <summary>
        /// Collision mesh
        /// </summary>
        public CollisionBox collisionShape;

        /// <summary>
        /// _Explosion sound
        /// </summary>
        private SoundEffect explosionSound;

        /// <summary>
        /// Position
        /// </summary>
        public Vector3 InitialPosition 
        {
            get;
            set;
        } // InitialPosition

        /// <summary>
        /// Rotation
        /// </summary>
        public Vector3 InitialRotation 
        {
            get;
            set;
        } // InitialRotation

        /// <summary>
        /// Initial acceleration
        /// </summary>
        public Vector3 InitialAcceleration { get; set; } // InitialAcceler

        /// <summary>
        /// Initial force impulse
        /// </summary>
        public Vector3 InitialForceImpulse { get; set; } // InitialForceIm

        /// <summary>
        /// Initial torque impulse
        /// </summary>
        public Vector3 InitialTorqueImpulse { get; set; } // InitialTorque

        /// <summary>
        /// Model
        /// </summary>
        private Model model { get; set; } // Model

        /// <summary>
        /// _Explosion projectile
        /// </summary>
        private RockExplosionProjectile explosionProjectile;

        /// <summary>
        /// Explosion particles
        /// </summary>
        public ParticleSystem ExplosionParticles { get; set; } // Explosio

        /// <summary>
        /// Explosion smoke particles
        /// </summary>
        public ParticleSystem ExplosionSmokeParticles { get; set; } // ExplosionSmokeParticles

        /// <summary>
        /// Projectile trail particles
        /// </summary>
        public ParticleSystem ProjectileTrailParticles { get; set; } // ProjectileTrailParticles

        /// <summary>
        /// Collection
        /// </summary>
        public List<RockComponent> RockCollection { get; set; } // Collection

        /// <summary>
        /// Physics
        /// </summary>
        public PhysicsComponent Physics { get; set; } // Physics

        /// <summary>
        /// World
        /// </summary>
        public Matrix World
        {
            get
            {
                Vector3 rotation = collisionShape.CollisionGroup.RigidBody.Rotation;
                return Matrix.CreateScale(RockScale) *
                    Matrix.CreateRotationX(rotation.X) *
                    Matrix.CreateRotationY(rotation.Y) *
                    Matrix.CreateRotationZ(rotation.Z) *
                    Matrix.CreateTranslation(collisionShape.Position);
            } // get
        } // World

        /// <summary>
        /// Create rock component
        /// </summary>
        public RockComponent(Game game)
            : base(game)
        {
        } // RockComponent

        /// <summary>
        /// Update bounding box
        /// </summary>
        private void UpdateBoundingBox(BoundingBox box, Vector3 position)
        {
            // Get the Center of the Box 
            Vector3 center = box.Max - box.Min;

            // Get the Offset from the position (center of model) 
            Vector3 offset = position - center;

            // Adjust Box to match model 
            box.Max += offset;
            box.Min += offset;
        } // UpdateBoundingBox

        /// <summary>
        /// Load content
        /// </summary>
        protected override void LoadContent()
        {
            model = Game.Content.Load<Model>("Models/LargeRock");
            collisionShape = new CollisionBox();
            collisionShape.Tag = this;

            BoundingBox boundingBox = new BoundingBox();
            foreach (ModelMesh mesh in model.Meshes)
            {
                VertexPositionNormalTexture[] vertices =
                  new VertexPositionNormalTexture[mesh.VertexBuffer.SizeInBytes / VertexPositionNormalTexture.SizeInBytes];
                mesh.VertexBuffer.GetData<VertexPositionNormalTexture>(vertices);
                Vector3[] vertexs = new Vector3[vertices.Length];
                for (int index = 0; index < vertexs.Length; index++)
                    vertexs[index] = vertices[index].Position;
                boundingBox = BoundingBox.CreateMerged(boundingBox,
                  BoundingBox.CreateFromPoints(vertexs));
            } // foreach
            UpdateBoundingBox(boundingBox, InitialPosition);
            collisionShape.Extents = boundingBox.GetExtents() * RockScale * 0.6f;
            RigidBody rockBody = new RigidBody();
            rockBody.VelocityLimit = 50;
            rockBody.Acceleration = InitialAcceleration;
            rockBody.CollisionGroup.CollisionShapes.Add(collisionShape);
            rockBody.CalculateMassProperties(1.0f);
            rockBody.ApplyForceImpulse(InitialForceImpulse);
            rockBody.ApplyTorqueImpulse(InitialTorqueImpulse);

            Vector3 position = InitialPosition;
            Matrix rotation = Matrix.CreateFromYawPitchRoll(InitialRotation.X, InitialRotation.Y, InitialRotation.Z);
            rockBody.UpdateTransform(ref position, ref rotation);

            Physics.RigidBodies.Add(rockBody);

            explosionSound = Game.Content.Load<SoundEffect>("Sounds/ExplodeLarge");
            base.LoadContent();
        } // LoadContent

        /// <summary>
        /// Update rock position using constraints
        /// </summary>
        private void UpdateRockPositionUsingConstraints()
        {
            Viewport viewport = GraphicsDevice.Viewport;
            SimpleCamera camera = (SimpleCamera)Game.Services.GetService(typeof(SimpleCamera));

            Matrix cameraWorld = Matrix.Identity;
            Vector3 viewportPosition = viewport.Project(collisionShape.Position,
                camera.Projection, camera.View, cameraWorld);

            int margin = 5;
            int fix = 5;
            Vector2 viewportPoint = new Vector2(viewportPosition.X, viewportPosition.Y);
            Vector2 newPoint = viewportPoint;

            if (viewportPosition.X < margin)
                newPoint.X = viewport.Width - margin - fix;

            if (viewportPosition.X > viewport.Width - margin)
                newPoint.X = margin + fix;

            if (viewportPosition.Y < margin)
                newPoint.Y = viewport.Height - margin - fix;

            if (viewportPosition.Y > viewport.Height - margin)
                newPoint.Y = margin + fix;

            if (newPoint != viewportPoint)
            {
                Vector3 output = viewport.Unproject(new Vector3(newPoint.X, newPoint.Y, viewportPosition.Z),
                    camera.Projection, camera.View, cameraWorld);
                output.Z = 0;
                collisionShape.Position = output;
                collisionShape.CollisionGroup.RigidBody.Position = output;
            }
        } // UpdateRockPositionUsingConstraints

        /// <summary>
        /// Update
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            if (explosionProjectile != null)
            {
                if (!explosionProjectile.Update(gameTime))
                {
                    explosionProjectile = null;
                    RockCollection.Remove(this);
                    Physics.RigidBodies.Remove(collisionShape.CollisionGroup.RigidBody);
                }
            } // if
            else
            {
                UpdateRockPositionUsingConstraints();
            } // else
            base.Update(gameTime);
        } // Update

        /// <summary>
        /// Draw
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            SimpleCamera camera = (SimpleCamera)Game.Services.GetService(typeof(SimpleCamera));

            Matrix[] transformations = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transformations);

            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.TextureEnabled = true;
                    effect.Projection = camera.Projection;
                    effect.View = camera.View;
                    effect.World = /*transformations[mesh.ParentBone.Index] * */ World;
                }
                mesh.Draw();
            } // foreach

            base.Draw(gameTime);
        } // Draw

        /// <summary>
        /// Explode
        /// </summary>
        public void Explode()
        {
            explosionProjectile = new RockExplosionProjectile(ExplosionParticles, 
                                      ExplosionSmokeParticles, 
                                      collisionShape.CollisionGroup.RigidBody.Position, 
                                      Vector3.Zero, 
                                      this);
            explosionProjectile.ShouldExplode = true;
        } // Explode

        #region IExplosionListener Members

        /// <summary>
        /// On explosion
        /// </summary>
        public void OnExplosion(IProjectile projectile)
        {
            explosionSound.Play();
        } // OnExplosion

        #endregion
    } // class RockComponent
} // namespace SpaceRockTeam.SpaceRock.Components
